
/*
 * Copyright (C) Igor Sysoev
 * Copyright (C) Nginx, Inc.
 */


#include <ngx_config.h>
#include <ngx_core.h>


typedef struct
{
    ngx_flag_t pcre_jit;
} ngx_regex_conf_t;


static void *ngx_libc_cdecl ngx_regex_malloc(size_t size);
static void ngx_libc_cdecl  ngx_regex_free(void *p);
#if (NGX_HAVE_PCRE_JIT)
static void ngx_pcre_free_studies(void *data);
#endif

static ngx_int_t ngx_regex_module_init(ngx_cycle_t *cycle);

static void *ngx_regex_create_conf(ngx_cycle_t *cycle);
static char *ngx_regex_init_conf(ngx_cycle_t *cycle, void *conf);

static char *ngx_regex_pcre_jit(ngx_conf_t *cf, void *post, void *data);
static ngx_conf_post_t ngx_regex_pcre_jit_post = {ngx_regex_pcre_jit};


static ngx_command_t ngx_regex_commands[] = {

    {ngx_string("pcre_jit"), NGX_MAIN_CONF | NGX_DIRECT_CONF | NGX_CONF_FLAG,
     ngx_conf_set_flag_slot, 0, offsetof(ngx_regex_conf_t, pcre_jit),
     &ngx_regex_pcre_jit_post},

    ngx_null_command};


static ngx_core_module_t ngx_regex_module_ctx = {
    ngx_string("regex"), ngx_regex_create_conf, ngx_regex_init_conf};


ngx_module_t ngx_regex_module = {NGX_MODULE_V1,
                                 &ngx_regex_module_ctx, /* module context */
                                 ngx_regex_commands,    /* module directives */
                                 NGX_CORE_MODULE,       /* module type */
                                 NULL,                  /* init master */
                                 ngx_regex_module_init, /* init module */
                                 NULL,                  /* init process */
                                 NULL,                  /* init thread */
                                 NULL,                  /* exit thread */
                                 NULL,                  /* exit process */
                                 NULL,                  /* exit master */
                                 NGX_MODULE_V1_PADDING};


static ngx_pool_t *ngx_pcre_pool;
static ngx_list_t *ngx_pcre_studies;


void
ngx_regex_init(void)
{
    pcre_malloc = ngx_regex_malloc;
    pcre_free   = ngx_regex_free;
}


static ngx_inline void
ngx_regex_malloc_init(ngx_pool_t *pool)
{
    ngx_pcre_pool = pool;
}


static ngx_inline void
ngx_regex_malloc_done(void)
{
    ngx_pcre_pool = NULL;
}


ngx_int_t
ngx_regex_compile(ngx_regex_compile_t *rc)
{
    int              n, erroff;
    char            *p;
    pcre            *re;
    const char      *errstr;
    ngx_regex_elt_t *elt;

    ngx_regex_malloc_init(rc->pool);

    re = pcre_compile((const char *)rc->pattern.data, (int)rc->options, &errstr,
                      &erroff, NULL);

    /* ensure that there is no current pool */
    ngx_regex_malloc_done();

    if (re == NULL)
    {
        if ((size_t)erroff == rc->pattern.len)
        {
            rc->err.len = ngx_snprintf(rc->err.data, rc->err.len,
                                       "pcre_compile() failed: %s in \"%V\"",
                                       errstr, &rc->pattern)
                          - rc->err.data;
        }
        else
        {
            rc->err.len =
                ngx_snprintf(rc->err.data, rc->err.len,
                             "pcre_compile() failed: %s in \"%V\" at \"%s\"",
                             errstr, &rc->pattern, rc->pattern.data + erroff)
                - rc->err.data;
        }

        return NGX_ERROR;
    }

    rc->regex = ngx_pcalloc(rc->pool, sizeof(ngx_regex_t));
    if (rc->regex == NULL)
    {
        goto nomem;
    }

    rc->regex->code = re;

    /* do not study at runtime */

    if (ngx_pcre_studies != NULL)
    {
        elt = ngx_list_push(ngx_pcre_studies);
        if (elt == NULL)
        {
            goto nomem;
        }

        elt->regex = rc->regex;
        elt->name  = rc->pattern.data;
    }

    n = pcre_fullinfo(re, NULL, PCRE_INFO_CAPTURECOUNT, &rc->captures);
    if (n < 0)
    {
        p = "pcre_fullinfo(\"%V\", PCRE_INFO_CAPTURECOUNT) failed: %d";
        goto failed;
    }

    if (rc->captures == 0)
    {
        return NGX_OK;
    }

    n = pcre_fullinfo(re, NULL, PCRE_INFO_NAMECOUNT, &rc->named_captures);
    if (n < 0)
    {
        p = "pcre_fullinfo(\"%V\", PCRE_INFO_NAMECOUNT) failed: %d";
        goto failed;
    }

    if (rc->named_captures == 0)
    {
        return NGX_OK;
    }

    n = pcre_fullinfo(re, NULL, PCRE_INFO_NAMEENTRYSIZE, &rc->name_size);
    if (n < 0)
    {
        p = "pcre_fullinfo(\"%V\", PCRE_INFO_NAMEENTRYSIZE) failed: %d";
        goto failed;
    }

    n = pcre_fullinfo(re, NULL, PCRE_INFO_NAMETABLE, &rc->names);
    if (n < 0)
    {
        p = "pcre_fullinfo(\"%V\", PCRE_INFO_NAMETABLE) failed: %d";
        goto failed;
    }

    return NGX_OK;

failed:

    rc->err.len = ngx_snprintf(rc->err.data, rc->err.len, p, &rc->pattern, n)
                  - rc->err.data;
    return NGX_ERROR;

nomem:

    rc->err.len =
        ngx_snprintf(rc->err.data, rc->err.len,
                     "regex \"%V\" compilation failed: no memory", &rc->pattern)
        - rc->err.data;
    return NGX_ERROR;
}


ngx_int_t
ngx_regex_exec_array(ngx_array_t *a, ngx_str_t *s, ngx_log_t *log)
{
    ngx_int_t        n;
    ngx_uint_t       i;
    ngx_regex_elt_t *re;

    re = a->elts;

    for (i = 0; i < a->nelts; i++)
    {
        n = ngx_regex_exec(re[i].regex, s, NULL, 0);

        if (n == NGX_REGEX_NO_MATCHED)
        {
            continue;
        }

        if (n < 0)
        {
            ngx_log_error(NGX_LOG_ALERT, log, 0,
                          ngx_regex_exec_n " failed: %i on \"%V\" using \"%s\"",
                          n, s, re[i].name);
            return NGX_ERROR;
        }

        /* match */

        return NGX_OK;
    }

    return NGX_DECLINED;
}


static void *ngx_libc_cdecl
ngx_regex_malloc(size_t size)
{
    ngx_pool_t *pool;
    pool = ngx_pcre_pool;

    if (pool)
    {
        return ngx_palloc(pool, size);
    }

    return NULL;
}


static void ngx_libc_cdecl
ngx_regex_free(void *p)
{
    return;
}


#if (NGX_HAVE_PCRE_JIT)

static void
ngx_pcre_free_studies(void *data)
{
    ngx_list_t *studies = data;

    ngx_uint_t       i;
    ngx_list_part_t *part;
    ngx_regex_elt_t *elts;

    part = &studies->part;
    elts = part->elts;

    for (i = 0; /* void */; i++)
    {
        if (i >= part->nelts)
        {
            if (part->next == NULL)
            {
                break;
            }

            part = part->next;
            elts = part->elts;
            i    = 0;
        }

        if (elts[i].regex->extra != NULL)
        {
            pcre_free_study(elts[i].regex->extra);
        }
    }
}

#endif


static ngx_int_t
ngx_regex_module_init(ngx_cycle_t *cycle)
{
    int              opt;
    const char      *errstr;
    ngx_uint_t       i;
    ngx_list_part_t *part;
    ngx_regex_elt_t *elts;

    opt = 0;

#if (NGX_HAVE_PCRE_JIT)
    {
        ngx_regex_conf_t   *rcf;
        ngx_pool_cleanup_t *cln;

        rcf =
            (ngx_regex_conf_t *)ngx_get_conf(cycle->conf_ctx, ngx_regex_module);

        if (rcf->pcre_jit)
        {
            opt = PCRE_STUDY_JIT_COMPILE;

            /*
             * The PCRE JIT compiler uses mmap for its executable codes, so we
             * have to explicitly call the pcre_free_study() function to free
             * this memory.
             */

            cln = ngx_pool_cleanup_add(cycle->pool, 0);
            if (cln == NULL)
            {
                return NGX_ERROR;
            }

            cln->handler = ngx_pcre_free_studies;
            cln->data    = ngx_pcre_studies;
        }
    }
#endif

    ngx_regex_malloc_init(cycle->pool);

    part = &ngx_pcre_studies->part;
    elts = part->elts;

    for (i = 0; /* void */; i++)
    {
        if (i >= part->nelts)
        {
            if (part->next == NULL)
            {
                break;
            }

            part = part->next;
            elts = part->elts;
            i    = 0;
        }

        elts[i].regex->extra = pcre_study(elts[i].regex->code, opt, &errstr);

        if (errstr != NULL)
        {
            ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
                          "pcre_study() failed: %s in \"%s\"", errstr,
                          elts[i].name);
        }

#if (NGX_HAVE_PCRE_JIT)
        if (opt & PCRE_STUDY_JIT_COMPILE)
        {
            int jit, n;

            jit = 0;
            n   = pcre_fullinfo(elts[i].regex->code, elts[i].regex->extra,
                                PCRE_INFO_JIT, &jit);

            if (n != 0 || jit != 1)
            {
                ngx_log_error(NGX_LOG_INFO, cycle->log, 0,
                              "JIT compiler does not support pattern: \"%s\"",
                              elts[i].name);
            }
        }
#endif
    }

    ngx_regex_malloc_done();

    ngx_pcre_studies = NULL;

    return NGX_OK;
}


static void *
ngx_regex_create_conf(ngx_cycle_t *cycle)
{
    ngx_regex_conf_t *rcf;

    rcf = ngx_pcalloc(cycle->pool, sizeof(ngx_regex_conf_t));
    if (rcf == NULL)
    {
        return NULL;
    }

    rcf->pcre_jit = NGX_CONF_UNSET;

    ngx_pcre_studies = ngx_list_create(cycle->pool, 8, sizeof(ngx_regex_elt_t));
    if (ngx_pcre_studies == NULL)
    {
        return NULL;
    }

    return rcf;
}


static char *
ngx_regex_init_conf(ngx_cycle_t *cycle, void *conf)
{
    ngx_regex_conf_t *rcf = conf;

    ngx_conf_init_value(rcf->pcre_jit, 0);

    return NGX_CONF_OK;
}


static char *
ngx_regex_pcre_jit(ngx_conf_t *cf, void *post, void *data)
{
    ngx_flag_t *fp = data;

    if (*fp == 0)
    {
        return NGX_CONF_OK;
    }

#if (NGX_HAVE_PCRE_JIT)
    {
        int jit, r;

        jit = 0;
        r   = pcre_config(PCRE_CONFIG_JIT, &jit);

        if (r != 0 || jit != 1)
        {
            ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
                               "PCRE library does not support JIT");
            *fp = 0;
        }
    }
#else
    ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
                       "nginx was built without PCRE JIT support");
    *fp = 0;
#endif

    return NGX_CONF_OK;
}
